package com.sky.wsp.maven.plugin;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.jar.JarArchiveInputStream;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 工作10年的前阿里P7分享Java、算法、数据库方面的技术干货！坚信用技术改变命运，让家人过上更体面的生活！喜欢的请关注公众号：路人甲Java
 */
@Mojo(name = "license", defaultPhase = LifecyclePhase.PACKAGE)
public class LicenseMojo extends AbstractMojo {
    /**
     * 要显示的问候语。
     */
    @Parameter(defaultValue = "true")
    private boolean disable;
    /**
     * 当前Maven工程
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    /**
     * 原本JAR所在文件夹
     */
    @Parameter(property = "xjar.sourceDir", required = true, defaultValue = "${project.build.directory}")
    private File sourceDir;

    /**
     * 原本JAR名称
     */
    @Parameter(property = "xjar.sourceJar", required = true, defaultValue = "${project.build.finalName}.jar")
    private String sourceJar;

    /**
     * 生成JAR所在文件夹
     */
    @Parameter(property = "xjar.targetDir", required = true, defaultValue = "${project.build.directory}")
    private File targetDir;

    /**
     * 生成JAR名称
     */
    @Parameter(property = "xjar.targetJar", required = true, defaultValue = "${project.build.finalName}.xjar")
    private String targetJar;

    /**
     * 包含资源。
     * 使用Ant表达式，例如：
     * io/xjar/**
     * com/company/project/**
     * mapper/*Mapper.xml
     */
    @Parameter(property = "xjar.includes")
    private String[] includes;

    /**
     * 排除资源。
     * 使用Ant表达式，例如：
     * io/xjar/**
     * static/**
     * META-INF/resources/**
     */
    @Parameter(property = "xjar.excludes")
    private String[] excludes;

    /**
     * 加密完成后需要删除的资源
     * 支持Ant表达式，例如：
     * target/*.jar
     * ../module/target/*.jar
     */
    @Parameter(property = "xjar.deletes")
    private String[] deletes;


    @Override
    public void execute()  {
        Log log = this.getLog();
        URL[] urls = new URL[0];
        try {
            urls = new URL[]{ new File(project.getBuild().getOutputDirectory()).toURI().toURL()};
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        JarClassLoader classLoader = new JarClassLoader(urls);
        if (null != project) {
            String absolutePath = project.getBasedir().getAbsolutePath();
            System.out.println(absolutePath);
        }
        File src = new File(sourceDir, sourceJar);
        File dest = new File(targetDir, targetJar);
        log.info("Building license: " + dest + " for jar: " + src);

        try {
            FileInputStream fis = new FileInputStream(src);
            JarArchiveInputStream zis = new JarArchiveInputStream(fis);
            JarArchiveEntry entry;
            while ((entry = zis.getNextJarEntry()) != null) {
                String entryName = entry.getName();
                log.info("[" + (entry.isDirectory() ? "d" : "-") + "] " + entryName);
                if (entryName.endsWith("application.yml")) {
                    logFileContent(classLoader, zis);
                }
                if (entryName.endsWith("StrUtil.class")) {
                    inject(classLoader, zis);
                }
            }
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException " + e.getMessage());
        } catch (IOException e) {
            log.error("IOException " + e.getMessage());
        }
        log.info("wsp plugin execute!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
    }

    /**
     * 字节码注入
     */
    private void inject(JarClassLoader classLoader, JarArchiveInputStream zis) {
        Log log = this.getLog();
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = zis.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            byte[] classData = baos.toByteArray();

            Class<?> aClass = classLoader.defineClass("com.sky.wsp.spring.util.StrUtil", classData);
            ByteBuddy byteBuddy = new ByteBuddy();
            // 指定父类
            DynamicType.Unloaded<?> dynamicType = byteBuddy
//                    .subclass(aClass)
                    .redefine(aClass)
//                    .rebase(aClass)
                    // 指定生成类的名称
//                    .name("com.sky.wsp.spring.util.StrUtilLicense")
                    // 按名称 拦截该类的 toString()
                    .method(ElementMatchers.named("hi"))
//                            .method(ElementMatchers.any())
//                            .method(ElementMatchers.isDeclaredBy(aClass))
                    // 拦截方法调用 返回固定值
//                            .intercept(FixedValue.value("no hello license !!!"))
//                            .intercept(MethodDelegation.to(LicenseValidate.class))
                    .intercept(Advice.to(LicenseValidate.class))
                    // 产生字节码
                    .make();

            saveDynamicType(dynamicType);

            // classLoader 加载字节码到内存，一定要用上面自定义的classLoader，否则找不到要被代理的类
            Class<?> strUtilClazz = dynamicType.load(classLoader)
                    // 获得class对象
                    .getLoaded();

            log.info("==================: " + strUtilClazz.getName());
            Method[] declaredMethods = strUtilClazz.getDeclaredMethods();
            for (int i = 0; i < declaredMethods.length; i++) {
                log.info("==================method: " + declaredMethods[i].getName());
            }

            Object o = strUtilClazz.getDeclaredConstructor().newInstance();
            Method hi = strUtilClazz.getDeclaredMethod("hi", String.class);
            String helloAliya = (String) hi.invoke(o, "Aliya");
            log.info("==================: " + helloAliya);

//                    Method hello = strUtilClazz.getDeclaredMethod("hello", String.class);
//                    String helloStack = (String) hello.invoke(null, "Stack");
//                    log.info("==================: " + helloStack);

        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException " + e.getMessage());
        } catch (IOException e) {
            log.error("IOException " + e.getMessage());
        } catch (InvocationTargetException e) {
            log.error("InvocationTargetException " + e.getMessage());
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            log.error("IllegalAccessException " + e.getMessage());
        } catch (NoSuchMethodException e) {
            log.error("NoSuchMethodException " + e.getMessage());
            e.printStackTrace();
        } catch (InstantiationException e) {
            log.error("InstantiationException " + e.getMessage());
        }
    }

    private void saveDynamicType(DynamicType.Unloaded<?> dynamicType) {
        String file = project.getBasedir() + File.separator + "inject";
        try {
            // 将生成的class注入到jar
            dynamicType.saveIn(new File(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 打印文件内容
     */
    private void logFileContent(JarClassLoader classLoader, JarArchiveInputStream zis) {
        Log log = this.getLog();
        try {
            InputStreamReader isr = new InputStreamReader(zis, "utf-8");
            BufferedReader reader = new BufferedReader(isr);
            String line;
            while ((line = reader.readLine()) != null) {
                log.info(line);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
